encyklopedia.space

Proxy (JavaScript)

Proxy – mechanizm wprowadzony w ECMAScript 6 (znanym także jako ES6), który pozwala na przechwytywanie i redefiniowanie podstawowych operacji wykonywanych na obiektach i funkcjach w języku JavaScript. Proxy jest jednym z kluczowych elementów metaprogramowania w JavaScript.

Historia

Idea tzw. „przestrzeni nazw” i możliwości podmieniania zachowań obiektów pojawiła się w Javy już w latach 80‑tych, ale dopiero wersja ES6, opublikowana w 2015 roku, wprowadziła natywny i jednolity interfejs Proxy. Projekt tego rozwiązania był realizowany pod nazwą Harmony w ramach inicjatywy TC39.

Podstawowa składnia

const handler = {
    get(target, prop, receiver) {
        // kod przechwytujący odczyt właściwości
    },
    set(target, prop, value, receiver) {
        // kod przechwytujący zapis
    }
};

const proxy = new Proxy(targetObject, handler);

Obiekt targetObject – znany także jako cel – jest „opakowywany” przez Proxy. Każde odwołanie do właściwości, wywołanie funkcji czy konstrukcja nowego obiektu może zostać przechwycone przez tzw. handler – zestaw trapów (pułapek).

Najważniejsze trapy

  • get(target, prop, receiver) – odczyt wartości właściwości.
  • set(target, prop, value, receiver) – zapis wartości.
  • has(target, prop) – obsługa operatora in.
  • deleteProperty(target, prop) – usuwanie właściwości.
  • apply(target, thisArg, argumentsList) – wywołanie funkcji.
  • construct(target, argumentsList, newTarget) – użycie operatora new.
  • ownKeys(target) – zwraca listę własnych kluczy (używane m.in. przez Object.keys, for...in).
  • getPrototypeOf(target) oraz setPrototypeOf(target, proto) – dostęp i modyfikacja łańcucha prototypów.
  • defineProperty(target, prop, descriptor) – definiowanie własności przy pomocy Object.defineProperty.

Przykłady zastosowań

Walidacja danych

const user = { name: "Jan", age: 30 };
const validator = {
    set(target, prop, value) {
        if (prop === "age" && typeof value !== "number") {
            throw new TypeError("Wiek musi być liczbą");
        }
        target[prop] = value;
        return true;
    }
};

const userProxy = new Proxy(user, validator);
userProxy.age = 31;   // OK
userProxy.age = "trzydzieści"; // TypeError

Obsługa nieistniejących właściwości

const defaultValues = { a: 1, b: 2 };
const fallback = {
    get(target, prop) {
        return prop in target ? target[prop] : defaultValues[prop];
    }
};

const obj = new Proxy({}, fallback);
console.log(obj.a); // 1
console.log(obj.c); // undefined (brak w defaultValues)

Transparentne logowanie

function logAccess(target) {
    return new Proxy(target, {
        get(t, p, r) {
            console.log(`Odczyt: ${String(p)}`);
            return Reflect.get(t, p, r);
        },
        set(t, p, v, r) {
            console.log(`Zapis: ${String(p)} = ${v}`);
            return Reflect.set(t, p, v, r);
        }
    });
}

const arr = logAccess([1, 2, 3]);
arr[0];      // konsola: Odczyt: 0
arr[1] = 5;  // konsola: Zapis: 1 = 5

Powiązane obiekty i klasy

Do implementacji trapów najczęściej wykorzystuje się wbudowany obiekt Reflect, który zapewnia „domyślne” zachowanie operacji. Dzięki temu kod w trapie może najpierw wykonać dodatkową logikę, a następnie delegować do Reflect, aby nie zmienić semantyki.

Ograniczenia i pułapki

  • Proxy nie może być przetransponowany – nie da się utworzyć „proxy of a proxy” w taki sposób, aby oba zachowania się nakładały; najbliższym rozwiązaniem jest łańcuchowe opakowanie.
  • Nie wszystkie wbudowane operacje są przechwytywalne (np. wewnętrzne algorytmy silnika, które nie odwołują się do publicznego API).
  • Użycie Proxy może wpływać na wydajność, zwłaszcza przy intensywnym odczycie w pętlach.
  • Wszystkie własności, w tym symbole (Symbol), są objęte trapami, co może prowadzić do nieoczekiwanych rezultatów przy manipulacji ukrytymi własnościami.
  • W środowiskach sandboxowych (np. Node.js vm) dostęp do Proxy może być ograniczony ze względów bezpieczeństwa.

Bezpieczeństwo

Proxy umożliwia izolację i kontrolę dostępu do obiektów, co jest szczególnie przydatne w bibliotekach typu sandbox. Jednak niewłaściwe użycie może otworzyć luki, np. przy niewłaściwym przekazywaniu receiver w trapie get, co umożliwia obejście kontroli dostępu.

Podsumowanie

Proxy w JavaScript to potężny mechanizm pozwalający na dynamiczne modyfikowanie zachowań obiektów i funkcji. Dzięki trapom i obiektowi Reflect programiści mogą wprowadzać walidację, logowanie, wirtualizację danych oraz tworzyć zaawansowane API, zachowując jednocześnie pełną kontrolę nad kosztami wydajnościowymi i aspektami bezpieczeństwa.

Patrz także